import jsQR, { QRCode } from 'jsqr';

class QRcodeDecoder {
    canvasEle: HTMLCanvasElement;

    canvasCtx2D: CanvasRenderingContext2D | null;

    videoEle: HTMLVideoElement;

    /**
     * 从图片中进行二维码解码
     * @param image 图片的html节点或者dataURL
     */
    public async decodeByImage(image: HTMLImageElement | string): Promise<QRCode | null> {
      const imgEl = typeof image === 'string' ? await this.createImage(image) : image;
      const { width, height } = imgEl;
      const imageData = this.createImageData(imgEl, width, height);
      return this.getCode(imageData);
    }

    /**
     * 在视频中进行解码
     * @param videoEle VideoHTML节点
     * @param interval 解码时间间隔（单位ms） 默认为 500
     */
    public async decodeByVideo(
      videoEle: HTMLVideoElement,
      interval?: number,
    ): Promise<QRCode | null> {
      this.videoEle = videoEle;
      const code = await this.decodeByFrame(interval);
      if (code) { return code; }
      return null;
    }

    /**
     * 在视频中按照指定时间间隔进行一次二维码识别
     * @param interval 默认为500ms
     */
    private decodeByFrame(interval = 500): Promise<QRCode> {
      return new Promise((resolve, reject) => {
        const { videoWidth, videoHeight } = this.videoEle;
        if (!videoWidth || !videoHeight) {
          reject(new Error('Video Source is ERROR!'));
        }

        const imageData = this.createImageData(this.videoEle, videoWidth, videoHeight);

        const code = this.getCode(imageData);

        if (code) {
          resolve(code);
        } else {
          setTimeout(() => {
            resolve(this.decodeByFrame(interval));
          }, interval);
        }
      });
    }

    /**
     * 通过imageData生成二维码解析对象
     * @param imageData 根据canvas获取的imageData
     */
    getCode(imageData: ImageData | void): QRCode | null {
      const { width, height } = this.canvasEle;
      if (imageData) {
        const code = jsQR(imageData.data, width, height);
        if (code) { return code; }
      }
      return null;
    }

    /**
     * 获取指定图片或视频帧的ImageData
     * @param el 要画入的实体 可以为视频或图片HTML节点
     * @param width 实体的高度
     * @param height 实体的高度
     */
    createImageData(
      el: HTMLVideoElement | HTMLImageElement,
      width: number,
      height: number,
    ): ImageData {
      this.generateCanvas();
      this.drawToCanvas(el, width, height);
      if (!this.canvasCtx2D) { throw Error('canvasCtx2D was undefined'); }
      return this.canvasCtx2D.getImageData(0, 0, width, height);
    }

    /**
     * 初始化或清空画布
     */
    private generateCanvas() {
      if (!this.canvasEle) {
        this.canvasEle = document.createElement('canvas');
      }
      this.canvasCtx2D = this.canvasEle.getContext('2d');
      const { height, width } = this.canvasEle;
      this.canvasCtx2D && this.canvasCtx2D.clearRect(0, 0, height, width); // 清空画布
    }

    /**
     * 根据dataURL创建一个img元素
     * @param dataURL Image的src参数
     */
    private createImage(dataURL: string): Promise<HTMLImageElement> {
      const img = new Image();
      img.src = dataURL;
      return new Promise((resolve, reject) => {
        img.onload = () => {
          resolve(img);
        };
        img.onerror = reject;
      });
    }

    /**
     * 将image 或 video 画入到Canvas中
     * @param el 要画入的实体
     * @param width 实体的高度
     * @param height 实体的高度
     */
    private drawToCanvas(
      el: HTMLVideoElement | HTMLImageElement,
      width: number,
      height: number,
    ): void {
      this.generateCanvas();
      this.canvasEle.width = width;
      this.canvasEle.height = height;
      this.canvasCtx2D && this.canvasCtx2D.drawImage(el, 0, 0, width, height);
    }
}

export default QRcodeDecoder;
